package com.joelemon.mockinfluxdbdata.datamock.v2.mockunit;

import com.joelemon.mockinfluxdbdata.common.SystemException;
import com.joelemon.mockinfluxdbdata.datamock.v2.MockValue;
import com.joelemon.mockinfluxdbdata.datamock.v2.enums.MockValueType;
import com.joelemon.mockinfluxdbdata.datamock.v2.enums.NumericalStrategy;
import com.joelemon.mockinfluxdbdata.datamock.v2.interfaces.MockValueUnit;
import com.joelemon.mockinfluxdbdata.utils.CalcUtil;
import lombok.Data;

import java.text.NumberFormat;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors;

/**
 * @Description: 元mock- 数值类
 * 数值类型：（整型，浮点型）
 * 1. 固定值
 * 2. 随机
 * 3. 线性单调增、减（固定值增减、随机值增减增）
 * <p>
 * 数值边界策略（初始值重定向）
 * <p>
 * {
 * "type": "Integer,Decimal",
 * "range": "[0,1),{1,2}",
 * "strategy": "increase,decrease,random,sequence",
 * "factor": {},
 * "refix": {},
 * }
 * @Author: HJY
 * @Date: 2023/11/23
 */
@Data
public class MockNumericalValUnit extends DefaultMockValueUnit implements MockValueUnit {
    private RunningInfo runningInfo;
    @Data
    private static class RunningInfo {
        private AtomicReference<Double> atomicVal;
        private Integer index;
        private Double max;
        private Boolean equalsMax;
        private Double min;
        private Boolean equalsMin;
        private Double[] values;
        private Integer loopCount;
        
        public Double getVal() {
            if (atomicVal != null) {
                return atomicVal.get();
            }
            return null;
        }

        public void setVal(Double newVal) {
            if (atomicVal != null) {
                atomicVal.set(newVal);
            } else {
                atomicVal = new AtomicReference<>(newVal);
            }
        }

        public boolean loop() {
            if (loopCount != null) loopCount = loopCount - 1;
            return loopCount != null && loopCount >= 0;
        }
    }

    @Override
    public MockValueType[] getValueType() {
        return new MockValueType[]{MockValueType.INTEGER, MockValueType.DECIMAL};
    }

    public Double dealWithOutOfRange(Double value) {
        if (value > runningInfo.getMax() || (!runningInfo.getEqualsMax() && value.equals(runningInfo.getMax()))) {
            return Double.parseDouble(getRefix().mockValue().toString());
        }
        if (value < runningInfo.getMin() || (!runningInfo.getEqualsMin() && value.equals(runningInfo.getMin()))) {
            return Double.parseDouble(getRefix().mockValue().toString());
        }
        return value;
    }

    private void initBaseVal() {
        if (runningInfo == null) {
            parseRange();
        }
        if (runningInfo.getAtomicVal() == null) {
            runningInfo.setAtomicVal(new AtomicReference<>());
            switch (getStrategy()) {
                case INCREASE -> {
                    runningInfo.setVal(runningInfo.getMin());
                }
                case DECREASE -> {
                    runningInfo.setVal(runningInfo.getMax());
                }
                case RANDOM -> {
                    handleRandom();
                }
                case SEQUENCE -> {
                    handleSequence();
                }
            }
        }
    }

    private void handleSequence() {
        if (runningInfo.getIndex() == null) {
            runningInfo.setIndex(0);
            runningInfo.setVal(runningInfo.getValues()[runningInfo.getIndex()]);
        } else {
            runningInfo.setIndex((runningInfo.getIndex() + 1) % runningInfo.getValues().length);
            runningInfo.setVal(runningInfo.getValues()[runningInfo.getIndex()]);
        }
    }
    private void handleRandom() {
        if (runningInfo.getValues() != null) {
            runningInfo.setVal(runningInfo.getValues()
                    [ThreadLocalRandom.current().nextInt(runningInfo.getValues().length)]);
        } else {
            if (getPrecision() > 0) {
                runningInfo.setVal(runningInfo.getMin() +
                        ((runningInfo.getMax() - runningInfo.getMin()) * ThreadLocalRandom.current().nextDouble()));
            } else {
                runningInfo.setVal((double)ThreadLocalRandom.current().nextInt(
                        runningInfo.getEqualsMin() ? runningInfo.getMin().intValue(): runningInfo.getMin().intValue() + 1,
                        runningInfo.getEqualsMax() ? runningInfo.getMax().intValue() + 1 : runningInfo.getMax().intValue()));
            }
        }
    }

    private MockValue getResultFromValue() {
        MockValue result = new MockValue();
        if (getPrecision() == 0) {
            result.setIntVal(runningInfo.getVal().intValue());
        } else {
            Double val = runningInfo.getVal();
            NumberFormat nbf = NumberFormat.getNumberInstance() ;
            nbf.setMaximumFractionDigits(getPrecision());
            String numberStr = nbf.format(val);
            result.setDecimalVal(Double.parseDouble(numberStr));
        }
        return result;
    }

    @Override
    public MockValue mockValue() {
        parseRange();
        if (runningInfo.atomicVal == null || runningInfo.atomicVal.get() == null) {
            initBaseVal();
            runningInfo.loop();
            return getResultFromValue();
        }
        if (runningInfo.loop()) {
            return getResultFromValue();
        }
        Double factorValue = null;
        if (getFactor() != null) {
            MockValue factorItemObj = getFactor().mockValue();
            factorValue = Double.parseDouble(factorItemObj.toString());
        }
        switch (getStrategy()) {
            case INCREASE -> {
                Double val = runningInfo.getVal();
                Double newVal = dealWithOutOfRange(CalcUtil.add(val,factorValue));
                runningInfo.setVal(newVal);
            }
            case DECREASE -> {
                Double val = runningInfo.getVal();
                Double newVal = dealWithOutOfRange(factorValue == null ? val : val - factorValue);
                runningInfo.setVal(newVal);
            }
            case RANDOM -> {
                handleRandom();
            }
            case SEQUENCE -> {
                handleSequence();
            }
        }
        runningInfo.setLoopCount(getLoop());
        return getResultFromValue();
    }

    public void parseRange() {
        if (runningInfo == null) {
            runningInfo = new RunningInfo();
            runningInfo.setLoopCount(getLoop());
            String range = getRange();
            String[] split = range.substring(1, range.length() - 1).split(",");
            if (range.startsWith("[") || range.startsWith("(")) {
                char start = range.charAt(0);
                char end = range.charAt(range.length() - 1);
                runningInfo.setEqualsMin(Objects.equals(start, '['));
                runningInfo.setEqualsMax(Objects.equals(end, ']'));
                if (split.length != 2) {
                    throw new SystemException("range is illegal: " + range);
                }
                runningInfo.setMin(Double.parseDouble(split[0]));
                runningInfo.setMax(Double.parseDouble(split[1]));
                if (runningInfo.getMin() >= runningInfo.getMax()) {
                    throw new SystemException("range is illegal: " + range);
                }
            } else if (range.startsWith("{")) {
                List<Double> valueList = Arrays.asList(split)
                        .stream().filter(item -> item != null && item.matches("-?\\d+\\.?\\d*"))
                        .map(Double::parseDouble).collect(Collectors.toList());
                runningInfo.setValues(valueList.toArray(new Double[valueList.size()]));
            } else {
                throw new SystemException("range is illegal: " + range);
            }
        }
    }


    public boolean check() {
        return getPrecision() != null && getStrategy() != null && checkRange(getRange());
    }

    /**
     * [0,1),{1,2}
     *
     * @param range
     * @return
     */
    private boolean checkRange(String range) {
        if (range == null || range.length() == 0) {
            return false;
        }
        boolean valueRangeType = range.matches("^[\\[|\\(]-?\\d+\\.?\\d*,-?\\d+\\.?\\d*[\\]|\\)]$");
        boolean valueListType = range.matches("^\\{(-?\\d+\\.?\\d*,?)+}$");
        boolean strategyCheck = getStrategy() != null;
        //  "strategy": "increase,decrease,random,sequence
        // 单调需要有因子及重定值
        if (valueListType && strategyCheck &&
                ((NumericalStrategy.INCREASE.equals(getStrategy()) || NumericalStrategy.DECREASE.equals(getStrategy()))
                        && (getFactor() == null || getRefix() == null))) {
            strategyCheck = false;
        }
        // 值列表只能随机或顺序
        if (valueRangeType && strategyCheck && !(NumericalStrategy.SEQUENCE.equals(getStrategy())
                || NumericalStrategy.RANDOM.equals(getStrategy()))) {
            strategyCheck = false;
        }
        boolean check = valueRangeType || valueListType;
        if (!check) {
            throw new SystemException("range is illegal: " + range);
        }
        if (!strategyCheck) {
            throw new SystemException("strategy is illegal: " + range);
        }
        return true;

    }
}
